//==================================================================
//  Copyright (C) 2006-2007  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
///
///
///
///
///
//==================================================================

#include <stdio.h>
#include <stdarg.h>
#include <windows.h>
#include <gl/glew.h>
#include "psys.h"
#include "font.h"

//==================================================================
static BITMAPINFO	bmi;
static HBITMAP		hbmp;
static HDC			hdc;
static void			*datap;
static HFONT		_hfont;
static int			_ch_wds[126-32+1], _ch_he;
static int			_tail_he = 2;	// should be properly queried from the font !!!

static int			_base_list;
static u_int		_tx_id;
static int			_draw_begin_cnt = 0;

//==================================================================
static int alloc_offscreen( int w, int h )
{
RECT	rc;

	bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmi.bmiHeader.biWidth = w;
	bmi.bmiHeader.biHeight = -h;
	bmi.bmiHeader.biPlanes = 1;
	bmi.bmiHeader.biBitCount = 32;
	bmi.bmiHeader.biCompression = BI_RGB;
	bmi.bmiHeader.biSizeImage = 0;
	bmi.bmiHeader.biXPelsPerMeter = 1000;
	bmi.bmiHeader.biYPelsPerMeter = 1000;
	bmi.bmiHeader.biClrImportant = 0;
	bmi.bmiHeader.biClrUsed = 0;

	hbmp = CreateDIBSection( hdc, &bmi,
								 DIB_RGB_COLORS,
								 &datap,
								 NULL, 0 );

	if ( !datap )
		return -1;

	SelectObject( hdc, hbmp );

	rc.left = 0;
	rc.top = 0;
	rc.right = w;
	rc.bottom = h;
	FillRect( hdc, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH) );

	return 0;
}

//==================================================================
static void build_texture( int w, int h, void *datap )
{
u_char	*dp;
u_char	*tdatap;
int		x, y, t;

	glGenTextures( 1, &_tx_id );
	glBindTexture( GL_TEXTURE_2D, _tx_id );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
//	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
//	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

	if NOT( tdatap = (u_char *)PSYS_MALLOC( w * h * 4 ) )
		return;

	dp = (u_char *)datap;
	for (y=0; y < h; ++y)
		for (x=0; x < w; ++x, dp += 4)
		{
			t = ((int)dp[0] + dp[1] + dp[2]) / 3;
			PCLAMP( t, 0, 255 );
			dp[0] = 255;
			dp[1] = 255;
			dp[2] = 255;
			dp[3] = t;
		}

	memcpy( tdatap, datap, 4 * w * h );
	glTexImage2D( GL_TEXTURE_2D,
					0,  //mipmap level
					GL_RGBA8,
					w, h, //size
					0,  //border
					GL_RGBA, //element format
					GL_UNSIGNED_BYTE, //element type (data size)
					tdatap );  //pixel data
	SAFE_FREE( tdatap );
}

//==================================================================
static void build_texture_sub2x2( int w, int h, void *datap )
{
u_char	*dp;
u_char	*tdatap, *tdatap2;
int		x, y, t;

	glGenTextures( 1, &_tx_id );
	glBindTexture( GL_TEXTURE_2D, _tx_id );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
//	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
//	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

	PASSERT( (w & 1) == 0 && (h & 1) == 0 );

	if NOT( tdatap = (u_char *)PSYS_CMALLOC( w/2 * h/2 * 4 ) )
		return;

	tdatap2 = tdatap;

	dp = (u_char *)datap;
	for (y=0; y < h/2; ++y)
	{
		for (x=0; x < w/2; ++x)
		{
			t = (int)dp[0] + dp[1] + dp[2] + dp[4] + dp[5] + dp[6];
			dp += w * 4;
			t += (int)dp[0] + dp[1] + dp[2] + dp[4] + dp[5] + dp[6];
			dp -= w * 4;

			t /= 3 * 4;

			PCLAMP( t, 0, 255 );
			if ( t )
			{
				tdatap2[0] = 255;
				tdatap2[1] = 255;
				tdatap2[2] = 255;
				tdatap2[3] = t;
			}

			tdatap2 += 4;

			dp += 2 * 4;
		}

		dp += w * 4;
	}

	//memcpy( tdatap, datap, 4 * w * h );
	glTexImage2D( GL_TEXTURE_2D,
					0,  //mipmap level
					GL_RGBA8,
					w/2, h/2, //size
					0,  //border
					GL_RGBA, //element format
					GL_UNSIGNED_BYTE, //element type (data size)
					tdatap );  //pixel data
	SAFE_FREE( tdatap );
}

//==================================================================
static int get_next_power( int val )
{
	if ( val <= 8 )		return 8; else
	if ( val <= 16 )	return 16; else
	if ( val <= 32 )	return 32; else
	if ( val <= 64 )	return 64; else
	if ( val <= 128 )	return 128; else
	if ( val <= 256 )	return 256; else
	if ( val <= 512 )	return 512; else
	if ( val <= 1024 )	return 1024;
	
	return 2048;
}

//==================================================================
#define X_PAD		2
#define Y_PAD		2
#define FONT_SIZE	(10)
//#define DOSUBSAMPLE

static void try_fit_n_wide( int max_tex_width )
{
	int		i;
	int		cur_x, cur_y;
	int		cur_line_max_h;
	SIZE	sz;
	TCHAR	buff[4];
	float	coex, coey;
	int		w, h;
	float	out_ch_w, out_ch_h;
	float	tx1, ty1, tx2, ty2;

	int		min_ch_wd;


	min_ch_wd = GetTextExtentPoint32( hdc, _T("I"), 1, &sz );

	cur_line_max_h = 0;
	cur_x = cur_y = 0;
	for (i=32; i <= 126; ++i)
	{
		_stprintf_s( buff, _T("%c"), i );
		GetTextExtentPoint32( hdc, buff, 1, &sz );
		if ( sz.cy > cur_line_max_h )
			cur_line_max_h = sz.cy;

		// $$$ hack !
		if ( i == '.' && sz.cx > 6 )
			sz.cx = 6;
		//if ( i != '.' && sz.cx < min_ch_wd )
		//	sz.cx = min_ch_wd;

#ifdef DOSUBSAMPLE
		sz.cx = (sz.cx + 1) & ~1;
#endif

		if ( sz.cx + cur_x >= max_tex_width )
		{
			cur_x = 0;
			cur_y += cur_line_max_h + Y_PAD;
#ifdef DOSUBSAMPLE	// when using subsample, make sure all characters are aligned starting to even Y
			cur_y = (cur_y + 1) & ~1;
#endif
			cur_line_max_h = sz.cy;
		}

		cur_x += sz.cx + X_PAD;
	}

	w = max_tex_width;
	h = get_next_power( cur_y + cur_line_max_h );
	coex = 1.0f / w;
	coey = 1.0f / h;

	alloc_offscreen( w, h );

	SetTextColor( hdc, 0xffffff );
	SetBkColor( hdc, 0x00000000 );
	_base_list = glGenLists( 126-32+1 );

	cur_line_max_h = 0;
	_ch_he = 0;
	cur_x = cur_y = 0;
	for (i=32; i <= 126; ++i)
	{
		_stprintf_s( buff, _T("%c"), i );
		GetTextExtentPoint32( hdc, buff, 1, &sz );

		// $$$ hack !
		if ( i == '.' && sz.cx > 6 )
			sz.cx = 6;
		//if ( i != '.' && sz.cx < min_ch_wd )
		//	sz.cx = min_ch_wd;
#ifdef DOSUBSAMPLE
		sz.cx = (sz.cx + 1) & ~1;
#endif

		out_ch_w = sz.cx;
		out_ch_h = sz.cy;

#ifdef DOSUBSAMPLE
		out_ch_w /= 2.0f;
		out_ch_h /= 2.0f;
#endif

		if ( sz.cx + cur_x >= max_tex_width )
		{
			cur_x = 0;
			cur_y += cur_line_max_h + Y_PAD;
#ifdef DOSUBSAMPLE	// when using subsample, make sure all characters are aligned starting to even Y
			cur_y = (cur_y + 1) & ~1;
#endif
			cur_line_max_h = sz.cy;
		}

		if ( sz.cy > cur_line_max_h )
			cur_line_max_h = sz.cy;

		if ( out_ch_h > _ch_he )
			_ch_he = (int)out_ch_h;

		TextOut( hdc, cur_x, cur_y, buff, 1 );

		_ch_wds[i-32] = (int)out_ch_w;
		glNewList( _base_list + i-32, GL_COMPILE );
			
			tx1 = (cur_x +		   0.5f*0) * coex;
			tx2 = (cur_x + sz.cx - 0.5f*0) * coex;

			ty1 = (cur_y +		   0.5f*0) * coey;
			ty2 = (cur_y + sz.cy - 0.5f*0) * coey;

			glBegin( GL_QUADS );
			glTexCoord2f( tx1, ty1 );		glVertex2f( 0, 0 );
			glTexCoord2f( tx2, ty1 );		glVertex2f( out_ch_w, 0 );
			glTexCoord2f( tx2, ty2 );		glVertex2f( out_ch_w, out_ch_h );
			glTexCoord2f( tx1, ty2 );		glVertex2f( 0, out_ch_h );
			glEnd();
		glEndList();

		cur_x += sz.cx + X_PAD;
	}
#ifdef DOSUBSAMPLE
	build_texture_sub2x2( w, h, datap );
#else
	build_texture( w, h, datap );
#endif
}

//==================================================================
void FONT_Create()
{
	LOGFONT	lf;
	static bool	created;

	if ( created )
	{
		PASSERT( 0 );
		return;
	}
	created = true;

	if NOT( hdc = CreateCompatibleDC( GetDC(NULL) ) )
		return;

	memset( &lf, 0, sizeof(lf) );
	//strcpy_s( (LPSTR)&lf.lfFaceName, "MS Gothic" );
	_tcscpy_s( lf.lfFaceName, _T("Tahoma") );
	//lf.lfCharSet = DEFAULT_CHARSET;
	//lf.lfWeight = FW_BOLD;
	//lf.lfQuality = NONANTIALIASED_QUALITY;
	lf.lfPitchAndFamily = FF_SWISS;
	lf.lfHeight = -MulDiv( FONT_SIZE, GetDeviceCaps(hdc, LOGPIXELSY), 72);
	//lf.lfHeight = 13;
	//lf.lfWeight = 900;
	lf.lfCharSet = SHIFTJIS_CHARSET;

	_hfont = CreateFontIndirect( &lf );

	SelectObject( hdc, _hfont );

	try_fit_n_wide( 512 );
}

//==================================================================
int FONT_GetTexname()
{
	return _tx_id;
}

//==================================================================
int FONT_GetBaseList()
{
	return _base_list;
}

//==================================================================
void FONT_puts( const TCHAR *strp )
{
	int len = _tcslen( strp );

	glPushMatrix();
	//glTranslatef( -0.375f, -0.375f, 0 );
	for (int i=0; i < len; ++i)
	{
		if NOT( strp[i] >= 32 && strp[i] <= 126 )
			continue;

		glCallList( _base_list + strp[i]-32 );

		glTranslatef( _ch_wds[ strp[i]-32 ], 0, 0 );
	}
	glPopMatrix();
}

//==================================================================
static void _nomatrix_puts( const TCHAR *strp, float max_wd )
{
	float	cur_wd = 0;

	int	len = _tcslen( strp );
	for (int i=0; i < len; ++i)
	{
		if NOT( strp[i] >= 32 && strp[i] <= 126 )
			continue;

		float	ch_wd = _ch_wds[ strp[i]-32 ];
		
		cur_wd += ch_wd;
		if ( cur_wd >= max_wd )
		{
			return;
		}

		glCallList( _base_list + strp[i]-32 );
		glTranslatef( ch_wd, 0, 0 );
	}
}

//==================================================================
void FONT_vprintf( const TCHAR *strp, va_list valist )
{
	TCHAR	buff[4096];

	_vstprintf_s( buff, strp, valist );
	FONT_puts( buff );
}

//==================================================================
void FONT_printf( const TCHAR *strp, ... )
{
va_list	va;

	va_start( va, strp );
		FONT_vprintf( strp, va );
	va_end( va );
}

//==================================================================
int FONT_TextHeight()
{
	return _ch_he;
}

//==================================================================
int FONT_StringWidth( const TCHAR *strp )
{
int	wd;

	for (wd=0; *strp; ++strp)
	{
		if NOT( *strp >= 32 && *strp <= 126 )
			continue;

		wd += _ch_wds[ *strp - 32 ];
	}

	return wd;
}

//==================================================================
static int calc_y( int y0, int h, int align_y )
{
	if ( align_y < 0 )
		return y0 - _tail_he;						// top
	else
	if ( align_y > 0 )
		return y0 + h - _ch_he - _tail_he;			// bottom
	else
		return y0 + (h - _ch_he) / 2 - _tail_he;	// middle
}

//==================================================================
void FONT_BoxAlignPF( int align_x, int align_y, const PSYS::RectF &in_rect, int *out_xp, int *out_yp, const TCHAR *strp, ... )
{
	*out_yp = calc_y( in_rect._y, in_rect._h, align_y );

	if ( align_x < 0 )
	{
		*out_xp = in_rect._x;									// left
	}
	else
	{
	TCHAR	buff[4096];
	int		len, swd;
	va_list	va;

		va_start( va, strp );
			if ( (len = _vstprintf_s( buff, strp, va )) > 0 )
			{
				swd = FONT_StringWidth( buff );
			}
			PASSERT( len >= 0 );
		va_end( va );

		if ( align_x > 0 )
			*out_xp = in_rect._x + in_rect._w - swd;			// right
		else
			*out_xp = in_rect._x + (in_rect._w - swd) / 2;		// center
	}
}

//==================================================================
void FONT_puts_pos_ba( const PSYS::RectF &in_rect, int align_x, int align_y, const TCHAR *strp )
{
int	x, y, swd;

	y = calc_y( in_rect._y, in_rect._h, align_y );

	if ( align_x < 0 )
	{
		x = in_rect._x;									// left
	}
	else
	{
		swd = FONT_StringWidth( strp );

		if ( align_x > 0 )
			x = in_rect._x + in_rect._w - swd;			// right
		else
			x = in_rect._x + (in_rect._w - swd) / 2;	// center
	}

	glPushMatrix();
		glTranslatef( x, y, 0 );
		_nomatrix_puts( strp, in_rect._w );
	glPopMatrix();
}

//==================================================================
void FONT_printf_pos_ba( const PSYS::RectF &in_rect, int align_x, int align_y, const TCHAR *strp, ... )
{
	TCHAR	buff[4096];
	int		len;
	va_list	va;

	va_start( va, strp );

		if ( (len = _vstprintf_s( buff, strp, va )) > 0 )
		{
			buff[ _countof(buff)-1 ] = 0;
			FONT_puts_pos_ba( in_rect, align_x, align_y, buff );
		}
		PASSERT( len >= 0 );

	va_end( va );
}

//==================================================================
void FONT_DrawBegin()
{
	glPushAttrib( GL_ENABLE_BIT | GL_TEXTURE_BIT ); 

	glEnable( GL_TEXTURE_2D );
	glBindTexture( GL_TEXTURE_2D, FONT_GetTexname() );

	glMatrixMode( GL_TEXTURE );
	glLoadIdentity();

	glMatrixMode( GL_MODELVIEW );
	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

	PASSERT( _draw_begin_cnt == 0 );
	++_draw_begin_cnt;
}

//==================================================================
void FONT_DrawEnd()
{
	PASSERT( _draw_begin_cnt == 1 );
	--_draw_begin_cnt;

	glPopAttrib();
}
